/* eslint-disable */

// To know how the filter work you'd better visit the following links
// https://github.com/vuejs/vue-devtools/blob/dev/packages/app-frontend/src/views/vuex/VuexStateInspector.vue
// https://github.com/vuejs/vue-devtools/blob/dev/packages/shared-utils/src/util.js

export function sortByKey(state) {
  return (
    state &&
    state.slice().sort((a, b) => {
      if (a.key < b.key) return -1
      if (a.key > b.key) return 1
      return 0
    })
  )
}

export function isPlainObject(obj) {
  return Object.prototype.toString.call(obj) === '[object Object]'
}

/**
 * Compares two values
 * @param {*} value Mixed type value that will be cast to string
 * @param {string} searchTerm Search string
 * @returns {boolean} Search match
 */
function compare(value, searchTerm) {
  return ('' + value).toLowerCase().indexOf(searchTerm) !== -1
}

/**
 * Searches a key or value in the object, with a maximum deepness
 * @param {*} obj Search target
 * @param {string} searchTerm Search string
 * @returns {boolean} Search match
 */
export function searchDeepInObject(obj, searchTerm) {
  const seen = new Map()
  const result = internalSearchObject(obj, searchTerm.toLowerCase(), seen, 0)
  seen.clear()
  return result
}

const SEARCH_MAX_DEPTH = 10

/**
 * Executes a search on each field of the provided object
 * @param {*} obj Search target
 * @param {string} searchTerm Search string
 * @param {Map<any,boolean>} seen Map containing the search result to prevent stack overflow by walking on the same object multiple times
 * @param {number} depth Deep search depth level, which is capped to prevent performance issues
 * @returns {boolean} Search match
 */
function internalSearchObject(obj, searchTerm, seen, depth) {
  if (depth > SEARCH_MAX_DEPTH) {
    return false
  }
  let match = false
  const keys = Object.keys(obj)
  let key, value
  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    value = obj[key]
    match = internalSearchCheck(searchTerm, key, value, seen, depth + 1)
    if (match) {
      break
    }
  }
  return match
}

/**
 * Checks if the provided field matches the search terms
 * @param {string} searchTerm Search string
 * @param {string} key Field key (null if from array)
 * @param {*} value Field value
 * @param {Map<any,boolean>} seen Map containing the search result to prevent stack overflow by walking on the same object multiple times
 * @param {number} depth Deep search depth level, which is capped to prevent performance issues
 * @returns {boolean} Search match
 */
function internalSearchCheck(searchTerm, key, value, seen, depth) {
  let match = false
  let result
  if (key === '_custom') {
    key = value.display
    value = value.value
  }
  // (result = specialTokenToString(value)) && (value = result)
  if (key && compare(key, searchTerm)) {
    match = true
    seen.set(value, true)
  } else if (seen.has(value)) {
    match = seen.get(value)
  } else if (Array.isArray(value)) {
    seen.set(value, null)
    match = internalSearchArray(value, searchTerm, seen, depth)
    seen.set(value, match)
  } else if (isPlainObject(value)) {
    seen.set(value, null)
    match = internalSearchObject(value, searchTerm, seen, depth)
    seen.set(value, match)
  } else if (compare(value, searchTerm)) {
    match = true
    seen.set(value, true)
  }
  return match
}

/**
 * Executes a search on each value of the provided array
 * @param {*} array Search target
 * @param {string} searchTerm Search string
 * @param {Map<any,boolean>} seen Map containing the search result to prevent stack overflow by walking on the same object multiple times
 * @param {number} depth Deep search depth level, which is capped to prevent performance issues
 * @returns {boolean} Search match
 */
function internalSearchArray(array, searchTerm, seen, depth) {
  if (depth > SEARCH_MAX_DEPTH) {
    return false
  }
  let match = false
  let value
  for (let i = 0; i < array.length; i++) {
    value = array[i]
    match = internalSearchCheck(searchTerm, null, value, seen, depth + 1)
    if (match) {
      break
    }
  }
  return match
}

export function isBasic(data) {
  // string number boolean null undefined symbol
  const basics = ['string', 'number', 'boolean', 'undefined', 'symbol']
  const type = typeof data
  return data === null || basics.includes(type)
}

const specialTypeRE = /^\[native (\w+) (.*?)(<>((.|\s)*))?\]$/
const symbolRE = /^\[native Symbol Symbol\((.*)\)\]$/

/**
 * Stringify/parse data using CircularJSON.
 */

export const UNDEFINED = '__undefined__'
export const INFINITY = '__infinity__'
export const NEGATIVE_INFINITY = '__negative_infinity__'
export const NAN = '__nan__'

export const SPECIAL_TOKENS = {
  true: true,
  false: false,
  undefined: UNDEFINED,
  null: null,
  '-Infinity': NEGATIVE_INFINITY,
  Infinity: INFINITY,
  NaN: NAN
}
const SPECIAL_TOKENS_REVERT = {
  true: true,
  false: false,
  [UNDEFINED]: undefined,
  null: null,
  [NEGATIVE_INFINITY]: -Infinity,
  [INFINITY]: Infinity,
  [NAN]: NaN
}

const special = [true, false, undefined, null, -Infinity, Infinity, NaN]
const specialRevert = [
  true,
  false,
  '__undefined__',
  null,
  '__infinity__',
  '__negative_infinity__',
  '__nan__'
]

/**
 * 替换对象中的特殊值，默认（display = true）是将基本数据类型转换为字符串
 * @param {Object} obj
 * @param {Boolean} display
 * @returns Object
 */
export function transform(obj, display = true) {
  let exception, tokens
  if (display) {
    exception = special
    tokens = SPECIAL_TOKENS
  } else {
    exception = specialRevert
    tokens = SPECIAL_TOKENS_REVERT
  }
  if (isPlainObject(obj)) {
    let cache = {}
    Object.keys(obj).forEach(key => {
      if (exception.includes(obj[key])) {
        cache[key] = tokens[obj[key]]
      } else {
        cache[key] = transform(obj[key], display)
      }
    })
    return cache
  } else if (Array.isArray(obj)) {
    let cache = []
    obj.forEach((key, index) => {
      if (exception.includes(key)) {
        cache[index] = tokens[key]
      } else {
        cache[index] = transform(obj[index], display)
      }
    })
    return cache
  } else {
    if (tokens[obj] !== undefined) {
      return tokens[obj]
    } else {
      return obj
    }
  }
}

export function transformSpecialTokens(str, display) {
  Object.keys(SPECIAL_TOKENS).forEach(key => {
    const value = JSON.stringify(SPECIAL_TOKENS[key])
    let search
    let replace
    if (display) {
      search = value
      replace = key
    } else {
      search = key
      replace = value
    }
    str = str.replace(new RegExp(search, 'g'), replace)
  })
  return str
}

export function parse(data, revive = false) {
  return revive
    ? parseCircularAutoChunks(data, reviver)
    : parseCircularAutoChunks(data)
}

function reviver(key, val) {
  return revive(val)
}

export function revive(val) {
  if (val === UNDEFINED) {
    return undefined
  } else if (val === INFINITY) {
    return Infinity
  } else if (val === NEGATIVE_INFINITY) {
    return -Infinity
  } else if (val === NAN) {
    return NaN
  } else if (symbolRE.test(val)) {
    const [, string] = symbolRE.exec(val)
    return Symbol.for(string)
  } else if (specialTypeRE.test(val)) {
    const [, type, string, , details] = specialTypeRE.exec(val)
    const result = new window[type](string)
    if (type === 'Error' && details) {
      result.stack = details
    }
    return result
  } else {
    return val
  }
}

export function parseCircularAutoChunks(data, reviver = null) {
  if (Array.isArray(data)) {
    data = data.join('')
  }
  const hasCircular = /^\s/.test(data)
  if (!hasCircular) {
    return arguments.length === 1 ? JSON.parse(data) : JSON.parse(data, reviver)
  } else {
    const list = JSON.parse(data)
    decode(list, reviver)
    return list[0]
  }
}
function decode(list, reviver) {
  let i = list.length
  let j, k, data, key, value, proto
  while (i--) {
    data = list[i]
    proto = Object.prototype.toString.call(data)
    if (proto === '[object Object]') {
      const keys = Object.keys(data)
      for (j = 0, k = keys.length; j < k; j++) {
        key = keys[j]
        value = list[data[key]]
        if (reviver) value = reviver.call(data, key, value)
        data[key] = value
      }
    } else if (proto === '[object Array]') {
      for (j = 0, k = data.length; j < k; j++) {
        value = list[data[j]]
        if (reviver) value = reviver.call(data, j, value)
        data[j] = value
      }
    }
  }
}
